﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Text.RegularExpressions;

namespace Devtm.Common
{



	public static partial class Tags
	{

		internal static IEnumerable<Tag> GetTagFrom(params Tag[] tags)
		{

			foreach (Tag item in tags)
			{

				yield return item;

                foreach (var item2 in item.Children)
                    foreach (var item3 in GetTagFrom(item2))
                        yield return item3;

            }
		}

	}


	[Serializable]
	[System.Diagnostics.DebuggerDisplay("{Label} - {Key}")]
	public class Tag
	{

		[NonSerialized]
		private List<Tag> __children = new List<Tag>();
		[NonSerialized]
		private string _label;


        internal Tag(string data)
        {
            Unserialize(data);
        }

        
		internal Tag(Tag parent = null)
		{
            try
            {
                IsLocal = true;
                Parent = parent;
                //Tags.Add(this);
                if (parent != null)
                    parent.Add(this);
            }
            catch (Exception)
            {
                throw;
            }
		}

        private void Add(Tag tag)
        {
            if (__children.Contains(tag))
                __children.Add(tag);
        }

		public Tag Parent { get; set; }

		internal bool Contains(Tag c) { return __children.Contains(c); }

		public IEnumerable<Tag> Children { get { return __children; } }

        private string _key;
        public string KeyString
        {
            get
            {
                if (string.IsNullOrEmpty(_key))
                {

                    StringBuilder s = new StringBuilder("[");

                    s.Append(Label);
                    s.Append(", ");
                    s.Append(Container);
                    if (Parent != null)
                    {
                        s.Append(", ");
                        s.Append(Parent.KeyString);
                    }
                    s.Append("]");
                    
                    _key = s.ToString();

                }

                return _key;
            }
        }

        private void Unserialize(string data)
        {
            _key = data;
            // TODO: unserialisation Tag
        }

		public string Label
		{
			get
			{
				
				if (string.IsNullOrEmpty(_label))
					SetName();

				return _label;

			}

		}

        public string Container { get; private set; }

		private void SetName()
		{

			foreach (var item in typeof(Tags).GetFields().Where(c => c.FieldType == typeof(Tag)))
			{
				Tag v = item.GetValue(null) as Tag;
                if (v != null && v == this)
                {
                    _label = item.Name;
                    Container = item.DeclaringType.FullName;
                }
			}

		}

        public override bool Equals(object obj)
        {
            if (base.Equals(obj))
                return true;

            Tag t1 = obj as Tag;

            if (t1 == null)
                return false;

            return t1.Container.Equals(Container) && t1.Label.Equals(Label);
        
        }

		public bool Equals(Tag t1, Tag t2)
		{

            if (t1 == null && t2 == null)
                return true;

            return t1.Container.Equals(t2.Container) && t1.Label.Equals(t2.Label);
		}

		public static bool operator ==(Tag[] t1, Tag t2)
		{
			int c = t1.Length;
			for (int i = 0; i < c; i++)
			{
				if (t1[i] == t2)
					return true;

                if (t2.Parent != null)
                {
                    var t = t1 == t2.Parent;
                    if (t)
                        return true;
                }

				
			}
			return false;
		}

        public static bool operator !=(Tag[] t1, Tag t2)
        {
            int c = t1.Length;
            for (int i = 0; i < c; i++)
            {
                if (t1[i] != t2)
                    return false;


                if (t2.Parent != null)
                {
                    var t = t1 == t2.Parent;
                    if (!t)
                        return false;
                }

            }
            return true;
        }


        public bool IsLocal { get;  private set; }

    }


    public enum LogLevelEnm
    {
        Debug,
        Trace,
        Verbose,
        Exception,
    }


	public static class Logger
	{

        internal static Dictionary<object, LoggerEvent> Listeners = new Dictionary<object, LoggerEvent>();

		internal static event EventHandler<LogEventArgs> LogEvent;

        internal static Exception Log<TSender, TLevel>(this TSender sender, TLevel level, Exception exception, params object[] parameters)
        {
            Log<TSender, TLevel>(sender, level, exception.Message, parameters, exception);
            return exception;
        }

		internal static void Log<TSender, TLevel>(this TSender sender, TLevel level, string log, params object[] parameters)
		{

			if (Logger.LogEvent != null)
			{

				List<Tag> c = new List<Tag>();
				List<object> p = new List<object>();
                Exception exception = null;

                CleanParameters(c, p, ref exception, parameters);

				Logger.LogEvent(sender, new LogEventArgs() { Exception = exception, Level = level, Log = log, Parameters = p.ToArray(), Tags = GetTagFrom(c.ToArray()).Distinct().ToArray() });

			}

		}


        private static Type[] _t = new Type[] 
        {
            typeof(string), typeof(decimal),
            typeof(byte), typeof(sbyte), 
            typeof(Int16), typeof(UInt16),
            typeof(Int32), typeof(UInt32),
            typeof(Int64), typeof(UInt64),
            typeof(Single), typeof(double),
            typeof(IntPtr), typeof(UIntPtr),
            typeof(DateTime), typeof(TimeSpan),
        };

        private static void CleanParameters(List<Tag> c, List<object> p,ref Exception exception, params object[] parameters)
        {

            foreach (var item in parameters)
            {
                if (_t.Contains(item.GetType()))
                    p.Add(item);

                else if (item is Exception)
                    exception = item as Exception;

                else if (item is Tag)
                    c.Add(item as Tag);

                else if (item is Array)
                    CleanParameters(c, p, ref exception, item);

                else if (item is IEnumerable)
                {
                    foreach (var item2 in item as IEnumerable)
                    {
                        CleanParameters(c, p, ref exception, item2);
                    }
                }

                else
                    p.Add(item.ToString());

            }
        }

        public static TLoggerEvent GetLogger<TLoggerEvent>(params Tag[] tags)
           where TLoggerEvent : LoggerEvent, new()
        {
            return GetLogger<TLoggerEvent>(null, tags);
        }
        public static TLoggerEvent GetLogger<TLoggerEvent>(this object self, params Tag[] tags)
			where TLoggerEvent : LoggerEvent, new()
		{

            if (self == null)
                self = Guid.NewGuid().ToString();

			var l = new TLoggerEvent();
            l.Register(self);
			l.Filter = GetTagFrom(tags).Distinct().ToArray();

			return l;

		}


		internal static IEnumerable<Tag> GetTagFrom(params Tag[] tags)
		{

			foreach (Tag item in tags)
			{

				yield return item;

				if (item.Parent != null)
					GetTagFrom(item.Parent);

			}
		}


	}


	public class LoggerEvent : IDisposable
	{

		private bool disposed;

		public LoggerEvent()
		{
            Logger.LogEvent += new EventHandler<LogEventArgs>(Logger_LogEvent);
		}

        internal void Register(object key)
        {
            Logger.Listeners.Add(key, this);
            this.Key = key;
        }

		void Logger_LogEvent(object sender, LogEventArgs e)
		{
			if (!disposed)
				if (Filter.Length == 0 || Filter.Intersect(e.Tags).Count() > 0)
					Log(sender, e);
		}

		protected virtual void Log(object sender, LogEventArgs e)
		{
			
		}

		public Tag[] Filter { get;  internal set; }

		~LoggerEvent()
		{
			Dispose(false);
		}

		public void Dispose()
		{
			Dispose(!disposed);
		}

		public void Dispose(bool disposing)
		{

			if (disposing)
			{

				if (Logger.Listeners.ContainsKey(this.Key))
                    Logger.Listeners.Remove(this.Key);

				disposed = true;

			}

		}

        public object Key { get; private set; }

    }


    
	public class LogEventArgs : EventArgs
	{

        private Regex reg = new Regex(@"\{\d+\}");

		public object Level { get; set; }
		public string Log { get; set; }
		public object[] Parameters { get; set; }
		public Tag[] Tags { get; set; }

        string _tostring;
        public override string ToString()
        {

            if (string.IsNullOrEmpty(_tostring))
            {

                var c = reg.Matches(Log).Count;

                if (c == 0)
                    _tostring = Log;
                else
                    _tostring = string.Format(Log, Parameters);
            
            }

            return _tostring;

        }


        public Exception Exception { get; set; }
    }



}
